/*
 * @Author       : wang chao
 * @Date         : 2023-05-17 07:55:32
 * @LastEditors: wangchao
 * @LastEditTime: 2024-08-27 10:52:45
 * @FilePath: \JD-RTT-Driver\storage\at24cxx.c
 * @Description  :
 * Copyright 2023 BingShan, All Rights Reserved.
 */
#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>

#include <string.h>
#include <stdlib.h>

#define DBG_ENABLE
#define DBG_SECTION_NAME "AT24X"
#define DBG_LEVEL        DBG_INFO
#define DBG_COLOR
#include <rtdbg.h>

#include "at24cxx.h"

#if (1)
// A0 A1 A2 connect GND
#define AT24CXX_ADDR (0xA0 >> 1)

#if (EE_TYPE == AT24C01)
#define AT24CXX_PAGE_BYTE       8
#define AT24CXX_MAX_MEM_ADDRESS 128
#elif (EE_TYPE == AT24C02)
#define AT24CXX_PAGE_BYTE       8
#define AT24CXX_MAX_MEM_ADDRESS 256
#elif (EE_TYPE == AT24C04)
#define AT24CXX_PAGE_BYTE       16
#define AT24CXX_MAX_MEM_ADDRESS 512
#elif (EE_TYPE == AT24C08)
#define AT24CXX_PAGE_BYTE       16
#define AT24CXX_MAX_MEM_ADDRESS 1024
#elif (EE_TYPE == AT24C16)
#define AT24CXX_PAGE_BYTE       16
#define AT24CXX_MAX_MEM_ADDRESS 2048
#elif (EE_TYPE == AT24C32)
#define AT24CXX_PAGE_BYTE       32
#define AT24CXX_MAX_MEM_ADDRESS 4096
#elif (EE_TYPE == AT24C64)
#define AT24CXX_PAGE_BYTE       32
#define AT24CXX_MAX_MEM_ADDRESS 8192
#elif (EE_TYPE == AT24C128)
#define AT24CXX_PAGE_BYTE       64
#define AT24CXX_MAX_MEM_ADDRESS 16384
#elif (EE_TYPE == AT24C256)
#define AT24CXX_PAGE_BYTE       64
#define AT24CXX_MAX_MEM_ADDRESS 32768
#elif (EE_TYPE == AT24C512)
#define AT24CXX_PAGE_BYTE       128
#define AT24CXX_MAX_MEM_ADDRESS 65536
#endif

static rt_err_t read_regs(at24cxx_device_t dev, rt_uint8_t len, rt_uint8_t* buf)
{
    struct rt_i2c_msg msgs;

    msgs.addr = AT24CXX_ADDR | dev->AddrInput;
    msgs.flags = RT_I2C_RD;
    msgs.buf = buf;
    msgs.len = len;

    if (rt_i2c_transfer(dev->i2c, &msgs, 1) == 1)
    {
        return RT_EOK;
    }
    else
    {
        return -RT_ERROR;
    }
}
uint8_t at24cxx_read_one_byte(at24cxx_device_t dev, uint16_t readAddr)
{
    rt_uint8_t buf[2];
    rt_uint8_t temp;
#if (EE_TYPE > AT24C16)
    buf[0] = (uint8_t)(readAddr >> 8);
    buf[1] = (uint8_t)readAddr;
    if (rt_i2c_master_send(dev->i2c, AT24CXX_ADDR, 0, buf, 2) == 0)
#else
    buf[0] = readAddr;
    if (rt_i2c_master_send(dev->i2c, AT24CXX_ADDR | dev->AddrInput, 0, buf, 1) == 0)
#endif
    {
        return RT_ERROR;
    }
    read_regs(dev, 1, &temp);
    return temp;
}

rt_err_t at24cxx_write_one_byte(at24cxx_device_t dev, uint16_t writeAddr, uint8_t dataToWrite)
{
    rt_uint8_t buf[3];
#if (EE_TYPE > AT24C16)
    buf[0] = (uint8_t)(writeAddr >> 8);
    buf[1] = (uint8_t)writeAddr;
    buf[2] = dataToWrite;
    if (rt_i2c_master_send(dev->i2c, AT24CXX_ADDR, 0, buf, 3) == 3)
#else
    buf[0] = writeAddr;  // cmd
    buf[1] = dataToWrite;
    // buf[2] = data[1];

    if (rt_i2c_master_send(dev->i2c, AT24CXX_ADDR | dev->AddrInput, 0, buf, 2) == 2)
#endif
        return RT_EOK;
    else
        return -RT_ERROR;
}

rt_err_t at24cxx_read_page(at24cxx_device_t dev, uint32_t readAddr, uint8_t* pBuffer, uint16_t numToRead)
{
    struct rt_i2c_msg msgs[2];
    uint8_t AddrBuf[2];

    msgs[0].addr = AT24CXX_ADDR | dev->AddrInput;
    msgs[0].flags = RT_I2C_WR;

#if (EE_TYPE > AT24C16)
    AddrBuf[0] = readAddr >> 8;
    AddrBuf[1] = readAddr;
    msgs[0].buf = AddrBuf;
    msgs[0].len = 2;
#else
    AddrBuf[0] = readAddr;
    msgs[0].buf = AddrBuf;
    msgs[0].len = 1;
#endif

    msgs[1].addr = AT24CXX_ADDR | dev->AddrInput;
    msgs[1].flags = RT_I2C_RD;
    msgs[1].buf = pBuffer;
    msgs[1].len = numToRead;

    if (rt_i2c_transfer(dev->i2c, msgs, 2) == 0)
    {
        return RT_ERROR;
    }

    return RT_EOK;
}

rt_err_t at24cxx_write_page(at24cxx_device_t dev, uint32_t wirteAddr, uint8_t* pBuffer, uint16_t numToWrite)
{
    struct rt_i2c_msg msgs[2];
    uint8_t AddrBuf[2];

    msgs[0].addr = AT24CXX_ADDR | dev->AddrInput;
    msgs[0].flags = RT_I2C_WR;

#if (EE_TYPE > AT24C16)
    AddrBuf[0] = wirteAddr >> 8;
    AddrBuf[1] = wirteAddr;
    msgs[0].buf = AddrBuf;
    msgs[0].len = 2;
#else
    AddrBuf[0] = wirteAddr;
    msgs[0].buf = AddrBuf;
    msgs[0].len = 1;
#endif

    msgs[1].addr = AT24CXX_ADDR | dev->AddrInput;
    msgs[1].flags = RT_I2C_WR | RT_I2C_NO_START;
    msgs[1].buf = pBuffer;
    msgs[1].len = numToWrite;

    if (rt_i2c_transfer(dev->i2c, msgs, 2) == 0)
    {
        return RT_ERROR;
    }

    return RT_EOK;
}

rt_err_t at24cxx_check(at24cxx_device_t dev)
{
    uint8_t temp;
    RT_ASSERT(dev);

    temp = at24cxx_read_one_byte(dev, AT24CXX_MAX_MEM_ADDRESS - 1);
    if (temp == 0x55)
        return RT_EOK;
    else
    {
        at24cxx_write_one_byte(dev, AT24CXX_MAX_MEM_ADDRESS - 1, 0x55);
        rt_thread_mdelay(EE_TWR);  // wait 5ms befor next operation
        temp = at24cxx_read_one_byte(dev, AT24CXX_MAX_MEM_ADDRESS - 1);
        if (temp == 0x55)
            return RT_EOK;
    }
    return RT_ERROR;
}

/**
 * This function read the specific numbers of data to the specific position
 *
 * @param bus the name of at24cxx device
 * @param ReadAddr the start position to read
 * @param pBuffer  the read data store position
 * @param NumToRead
 * @return RT_EOK  write ok.
 */
rt_err_t at24cxx_read(at24cxx_device_t dev, uint32_t ReadAddr, uint8_t* pBuffer, uint16_t NumToRead)
{
    rt_err_t result;
    RT_ASSERT(dev);

    if (ReadAddr + NumToRead > AT24CXX_MAX_MEM_ADDRESS)
    {
        return RT_ERROR;
    }

    result = rt_mutex_take(dev->lock, RT_WAITING_FOREVER);
    if (result == RT_EOK)
    {
        while (NumToRead)
        {
            *pBuffer++ = at24cxx_read_one_byte(dev, ReadAddr++);
            rt_thread_mdelay(EE_TWR);
            NumToRead--;
        }
    }
    else
    {
        LOG_E("The at24cxx could not respond  at this time. Please try again");
    }
    rt_mutex_release(dev->lock);

    return RT_EOK;
}

/**
 * This function read the specific numbers of data to the specific position
 *
 * @param bus the name of at24cxx device
 * @param ReadAddr the start position to read
 * @param pBuffer  the read data store position
 * @param NumToRead
 * @return RT_EOK  write ok.
 */
rt_err_t at24cxx_page_read(at24cxx_device_t dev, uint32_t ReadAddr, uint8_t* pBuffer, uint16_t NumToRead)
{
    rt_err_t result = RT_EOK;
    uint16_t pageReadSize = AT24CXX_PAGE_BYTE - ReadAddr % AT24CXX_PAGE_BYTE;

    RT_ASSERT(dev);

    if (ReadAddr + NumToRead > AT24CXX_MAX_MEM_ADDRESS)
    {
        return RT_ERROR;
    }

    result = rt_mutex_take(dev->lock, RT_WAITING_FOREVER);
    if (result == RT_EOK)
    {
        while (NumToRead)
        {
            if (NumToRead > pageReadSize)
            {
                if (at24cxx_read_page(dev, ReadAddr, pBuffer, pageReadSize))
                {
                    result = RT_ERROR;
                }

                ReadAddr += pageReadSize;
                pBuffer += pageReadSize;
                NumToRead -= pageReadSize;
                pageReadSize = AT24CXX_PAGE_BYTE;
            }
            else
            {
                if (at24cxx_read_page(dev, ReadAddr, pBuffer, NumToRead))
                {
                    result = RT_ERROR;
                }
                NumToRead = 0;
            }
        }
    }
    else
    {
        LOG_E("The at24cxx could not respond  at this time. Please try again");
    }

    rt_mutex_release(dev->lock);
    return result;
}

/**
 * This function write the specific numbers of data to the specific position
 *
 * @param bus the name of at24cxx device
 * @param WriteAddr the start position to write
 * @param pBuffer  the data need to write
 * @param NumToWrite
 * @return RT_EOK  write ok.at24cxx_device_t dev
 */
rt_err_t at24cxx_write(at24cxx_device_t dev, uint32_t WriteAddr, uint8_t* pBuffer, uint16_t NumToWrite)
{
    uint16_t i = 0;
    rt_err_t result;
    RT_ASSERT(dev);

    if (WriteAddr + NumToWrite > AT24CXX_MAX_MEM_ADDRESS)
    {
        return RT_ERROR;
    }

    result = rt_mutex_take(dev->lock, RT_WAITING_FOREVER);
    if (result == RT_EOK)
    {
        while (1)  // NumToWrite--
        {
            if (at24cxx_write_one_byte(dev, WriteAddr, pBuffer[i]) == RT_EOK)
            {
                rt_thread_mdelay(EE_TWR);
                WriteAddr++;
            }
            if (++i == NumToWrite)
            {
                break;
            }
            rt_thread_mdelay(EE_TWR);
        }
    }
    else
    {
        LOG_E("The at24cxx could not respond  at this time. Please try again");
    }
    rt_mutex_release(dev->lock);

    return RT_EOK;
}

/**
 * This function write the specific numbers of data to the specific position
 *
 * @param bus the name of at24cxx device
 * @param WriteAddr the start position to write
 * @param pBuffer  the data need to write
 * @param NumToWrite
 * @return RT_EOK  write ok.at24cxx_device_t dev
 */
rt_err_t at24cxx_page_write(at24cxx_device_t dev, uint32_t WriteAddr, uint8_t* pBuffer, uint16_t NumToWrite)
{
    rt_err_t result = RT_EOK;
    uint16_t pageWriteSize = AT24CXX_PAGE_BYTE - WriteAddr % AT24CXX_PAGE_BYTE;

    RT_ASSERT(dev);

    if (WriteAddr + NumToWrite > AT24CXX_MAX_MEM_ADDRESS)
    {
        return RT_ERROR;
    }

    result = rt_mutex_take(dev->lock, RT_WAITING_FOREVER);
    if (result == RT_EOK)
    {
        while (NumToWrite)
        {
            if (NumToWrite > pageWriteSize)
            {
                if (at24cxx_write_page(dev, WriteAddr, pBuffer, pageWriteSize))
                {
                    result = RT_ERROR;
                }
                rt_thread_mdelay(EE_TWR);  // wait 5ms befor next operation

                WriteAddr += pageWriteSize;
                pBuffer += pageWriteSize;
                NumToWrite -= pageWriteSize;
                pageWriteSize = AT24CXX_PAGE_BYTE;
            }
            else
            {
                if (at24cxx_write_page(dev, WriteAddr, pBuffer, NumToWrite))
                {
                    result = RT_ERROR;
                }
                rt_thread_mdelay(EE_TWR);  // wait 5ms befor next operation

                NumToWrite = 0;
            }
        }
    }
    else
    {
        LOG_E("The at24cxx could not respond  at this time. Please try again");
    }

    rt_mutex_release(dev->lock);
    return result;
}

/**
 * This function initializes at24cxx registered device driver
 *
 * @param dev the name of at24cxx device
 *
 * @return the at24cxx device.
 */
at24cxx_device_t at24cxx_init(const char* i2c_bus_name, uint8_t AddrInput)
{
    at24cxx_device_t dev;

    RT_ASSERT(i2c_bus_name);

    dev = rt_calloc(1, sizeof(struct at24cxx_device));
    if (dev == RT_NULL)
    {
        LOG_E("Can't allocate memory for at24cxx device on '%s' ", i2c_bus_name);
        return RT_NULL;
    }

    dev->i2c = rt_i2c_bus_device_find(i2c_bus_name);
    if (dev->i2c == RT_NULL)
    {
        LOG_E("Can't find at24cxx device on '%s' ", i2c_bus_name);
        rt_free(dev);
        return RT_NULL;
    }

    dev->lock = rt_mutex_create("mutex_at24cxx", RT_IPC_FLAG_FIFO);
    if (dev->lock == RT_NULL)
    {
        LOG_E("Can't create mutex for at24cxx device on '%s' ", i2c_bus_name);
        rt_free(dev);
        return RT_NULL;
    }

    dev->AddrInput = AddrInput;
    return dev;
}

/**
 * This function releases memory and deletes mutex lock
 *
 * @param dev the pointer of device driver structure
 */
void at24cxx_deinit(at24cxx_device_t dev)
{
    RT_ASSERT(dev);

    rt_mutex_delete(dev->lock);

    rt_free(dev);
}

//---------------------------------------------------------
static at24cxx_device_t at_dev_t = RT_NULL;

int at24c32_init(void)
{
    /* initialize the sensor when first probe */
    if (!at_dev_t || strcmp(at_dev_t->i2c->parent.parent.name, "i2c2"))
    {
        /* deinit the old device */
        if (at_dev_t)
        {
            at24cxx_deinit(at_dev_t);
        }
        at_dev_t = at24cxx_init("i2c2", 0);
        if (at_dev_t != RT_NULL)
        {
            LOG_I("at24cxx init success.");
        }
    }
    return 0;
}

INIT_COMPONENT_EXPORT(at24c32_init);

uint16_t ee_read_word(uint16_t addr)
{
    uint16_t word;
    uint8_t pReadBuf[2];
    if (at24cxx_read(at_dev_t, addr, pReadBuf, 2) == RT_EOK)
    {
        word = pReadBuf[0] << 8 | pReadBuf[1];
    };
    return word;
}

uint8_t ee_write_word(uint16_t addr, uint16_t word)
{
    uint8_t pWriteBuf[2];
    pWriteBuf[0] = word >> 8;
    pWriteBuf[1] = word & 0x00ff;
    at24cxx_write(at_dev_t, addr, pWriteBuf, 2);
    return 1;
}

uint8_t e2rm_storage_word(uint16_t location, int16_t word)
{
    if (ee_write_word(location, (uint16_t)word))
    {
        return 0;
    }
    return 1;
}

int16_t e2rm_load_word(uint16_t location)
{
    uint16_t ret;
    ret = (int16_t)ee_read_word(location);
    return (int16_t)ret;
}

#endif
